#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import os
import sys
import logging
import argparse
import re
import time

import ciparser
import dbserver
import csv

log_format = "%(levelname)s: %(filename)s-%(lineno)d/%(funcName)s: %(message)s"
log_format = "%(levelname)s: %(lineno)d/%(funcName)s: %(message)s"

logging.basicConfig(format=log_format)
log = logging.getLogger("[logdebug]")
log.setLevel(logging.INFO)


class IngoreRE:
    __force_ignore = [
        '^__stack_chk_fail$',
        '^_*assert_fail$',
        '^__aeabi_.*$',
        '^__gnu_mcount_nc$',
    ]

    __default_ignore = [
        '^v?s?c?n?print[kf]$', '^v?sscanf$',
        '^_*puts$', '^_*print[kf]?$', '^_*dev_printk$',
        '^_*seq_puts$', '^_*seq_printf$', '^_*SEQ_printf$',
        '^panic$', '^dump_stack$',
        '^_*warn(_printk)?',
        '^_*dynamic[_a-zA-Z0-9]*dbg$', '^_*dynamic[_a-zA-Z0-9]*debug$',
        '^_*dev_emerg$', '^_*dev_alert$', '^_*dev_crit$',
        '^_*dev_err$', '^_*dev_warn$', '^_*dev_notice$',
        '^_*dev_info$', '^_*dev_dbg$', '^_*dev_debug$'
        '^simple_strto.*$', '^_*kstrto.*',
        '^_*k?mem[a-z]*$', '^_*k?str[a-z]*$',
        # ^_*[kv][a-z]*alloc no $
        '^_*[kv][a-z]*alloc', '^_*k?v?free$',
        '^_*mutex_init$', '^_*mutex_lock$', '^_*mutex_trylock$', '^_*mutex_unlock$',
        '^_*rt_mutex_(un)?lock$', '^_*ww_mutex_(un)?lock$',
        '^_*spin_lock$', '^_*spin_unlock$', '^_*raw_spin_.*$',
        '^_*up$', '^_*down$',
        '^__traceiter_', '^perf_trace_',
        '^trace_raw_output_', '^trace_event_raw_event_',
    ]

    def __init__(self, ignore_list=None, force_ignore=None):
        self.ignore_list = self.__default_ignore
        self.ignore_list_force = self.__force_ignore
        if ignore_list:
            self.ignore_list = ignore_list
        if force_ignore:
            self.ignore_list_force = force_ignore

    @staticmethod
    def ignore_from_list(label, ignore_patterns):
        for p in ignore_patterns:
            if re.match(p, label):
                return True
        return False

    def ignore_force(self, label):
        return self.ignore_from_list(label, self.ignore_list_force)

    def ignore(self, label, force=True):
        # process static function
        label = label.split(':')[-1]
        if force and self.ignore_force(label):
            return True
        return self.ignore_from_list(label, self.ignore_list)

from abc import ABC, abstractmethod

class ProviderBase(ABC):
    @abstractmethod
    def rows(self):
        pass

    @abstractmethod
    def files(self):
        pass

    @abstractmethod
    def symbols(self):
        pass

    @abstractmethod
    def links(self):
        pass

    @abstractmethod
    def get_left(self, sym):
        pass

    @abstractmethod
    def get_right(self, sym):
        pass


class DictProvider():
    def __init__(self) -> None:
        pass

class CIProvider(ciparser.CIParserBase):
    def __init__(self, filename:str, suffix=None, sortkey=None):
        self.prov = None
        if suffix == 'ci':
            self.prov = ciparser.CIParser()
        elif suffix == 'cilist':
            self.prov = ciparser.MultiCIParser()

class CSVProvider():
    def __init__(self, filename:str, sortkey=None):
        pass

class DBProvider(ProviderBase, dbserver.DBServer):

    def __init__(self, path:str, table:str='', autocommit:bool=False):
        dbserver.DBServer.__init__(self, path, autocommit=autocommit)

        if table not in self.table_columns:
            if table:
                print('{} not in db'.format(table))
                table = ''
        if not table:
            tables = tuple(self.table_columns.keys())
            if len(tables) > 0:
                table = tables[0]
        self.table = table

    def rows(self, columns='*', conds:str='', ext:str=''):
        return self.query(self.table, columns, conds, ext)

    def files(self):
        rows = self.query(self.table, columns='FILE', ext='ORDER BY FILE')
        return {row[0] for row in rows}

    def symbols(self):
        return self.query(self.table, conds='TYPE="func"')

    def links(self):
        return self.query(self.table, conds='TYPE="call"')

    def create_table(self, table: str):
        if table in self.table_columns:
            raise ValueError()

        cols = [
            ('ID',      'INT', 'PRIMARY KEY', 'NOT NULL'),
            ('TYPE',    'TEXT'),
            ('FILE',    'TEXT'),
            ('LINE',    'INT'),
            ('COLUMN',  'INT'),
            ('SYMBOL',  'TEXT'),
            ('WSYMBOL', 'TEXT'),
            ('DST',     'TEXT'),
            ('WDST',    'TEXT'),
        ]
        self.create_table_list(table, cols)

    def from_ciparser(self, cip, table, commit=True):
        if table not in self.table_columns:
            raise ValueError()

        for idx, row in enumerate(cip.rows()):
            row = list(row)
            row.insert(0, idx)
            self.add_row_list(table, row)
        if commit:
            self.commit()

    def from_csv(self, fcsv, table, commit=True):
        if table not in self.table_columns:
            raise ValueError()

        with open(fcsv, 'r') as fd:
            reader = csv.reader(fd)
            header = next(reader)
            for idx, row in enumerate(reader):
                row = list(row)
                row.insert(0, idx)
                self.add_row_list(table, row)
            if commit:
                self.commit()

    def get_left(self, sym):
        conds = 'wdst="{}" AND wsymbol!=""'.format(sym)
        rows = self.query(self.table, 'wsymbol', conds)
        if rows:
            return [x[0] for x in rows]
        conds = 'dst="{}" AND wsymbol!=""'.format(sym)
        rows = self.query(self.table, 'wsymbol', conds)
        return [x[0] for x in rows]

    def get_right(self, sym):
        conds = 'wsymbol="{}" AND wdst!=""'.format(sym)
        rows = self.query(self.table, 'wdst', conds)
        if rows:
            return [x[0] for x in rows]
        conds = 'symbol="{}" AND wdst!=""'.format(sym)
        rows = self.query(self.table, 'wdst', conds)
        return [x[0] for x in rows]


def get_provider(type:str, infiles, kwargs:dict):
    prov = None
    if type == 'db':
        if isinstance(infiles, list):
            infiles = infiles[0]
        prov = DBProvider(infiles)
        if 'table' in kwargs:
            prov.table = kwargs['table']

    return prov

def out_db(args):
    db = DBProvider(args.output)

    table = args.table
    if table not in db.table_columns:
        db.create_table(table)

    infiles = args.files
    t_start = time.time()
    if args.infmt == 'csv':
        for fcsv in infiles:
            db.from_csv(fcsv, args.table)
    elif args.infmt in ('ci', 'cilist'):
        cifiles = []
        if args.infmt == 'ci':
            cifiles = args.files
        elif args.infmt == 'cilist':
            for filename in args.files:
                cifiles += ciparser.MultiCIParser.get_cilist(filename)
        cip = ciparser.MultiCIParser()
        print('parsing {} file(s)'.format(len(cifiles)))
        cip.parse_list(cifiles, args.prefix, merge=True)
        print('parsing use {t_use:.2f}s'.format(t_use=(time.time() - t_start)))
        print('rows: {}'.format(len(cip.rows())))
        db.from_ciparser(cip, table)

def main(args):
    if args.outfmt == 'db':
        out_db(args)
    else:
        print("outfmt only support db for now")
        os._exit(-1)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.description = '%(prog)s: Data Provider'

    parser.add_argument("-I", "--infmt", required=True,
                        help='Input formats(file suffix) are: csv/ci/db/cilist')

    parser.add_argument("-O", "--outfmt", required=True,
                        help='Output formats(file suffix) are: csv/db')

    parser.add_argument("-o", "--output", required=True,
                        help="output file")

    parser.add_argument("-p", "--prefix",
                        help='path prefix, usually same as source code path')

    parser.add_argument("-s", "--sort", type=str,
                        help='sort by type/file/symbol/callee')

    parser.add_argument("-t", "--table", type=str,
                        help='table name')

    parser.add_argument("files", nargs="+", type=str,
                        help="input files")

    args = parser.parse_args()
    print(args)
    if args.infmt not in ('csv', 'db', 'ci', 'cilist'):
        parser.print_help()
        os._exit(-1)
    if args.outfmt not in ('csv', 'db'):
        parser.print_help()
        os._exit(-1)
    main(args)
